package com.cyou.nad.dao.jdbc;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.TypeMismatchDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.NumberUtils;
import org.springframework.util.ReflectionUtils;

/**
 * 通过反射方式 实现RowMapper，达到自动装备javaBean的目的
 * 要求javabean 必须存在无参构造方法
 * 以sql的column 作为基准，如果sql的column 没有与之匹配的 java field ，那么则抛出异常
 * 名称匹配不区分大小写
 * 类型匹配如果不成功，会产生类型转换，如果仍然不匹配就会抛出异常
 * 
 * 此对象线程不安全，每次使用是都要new一个新对象。
 * 具体使用方式 参考单元测试ReflectionRowMapperTest
 * @author luzhaoman
 *
 * @param <T>  返回的javabean
 */
public class ReflectionRowMapper<T> implements RowMapper<T> {
	private Class<T> requiredType;
	private Map<String, Field> typeFields = new HashMap<String, Field>();

	public ReflectionRowMapper() {
		super();
	}

	public ReflectionRowMapper(Class<T> requiredType) {
		super();
		this.requiredType = requiredType;
		doTypeField();
	}

	private void doTypeField() {
		try {
			requiredType.newInstance();
		} catch (Exception e) {
			throw new InvalidDataAccessApiUsageException("创建返回对象失败，对象可能没有默认构造方法,requiredType:" + requiredType, e);
		}
		Field[] declaredFields = requiredType.getDeclaredFields();
		for (Field field : declaredFields) {
			typeFields.put(field.getName().toUpperCase(), field);
		}
	}

	public void setRequiredType(Class<T> requiredType) {
		this.requiredType = requiredType;
		doTypeField();
	}

	@Override
	public T mapRow(ResultSet rs, int rowNum) throws SQLException {
		ResultSetMetaData rsmd = rs.getMetaData();
		Map<Integer, Field> col2FieldMap = new HashMap<Integer, Field>();
		int cols = rsmd.getColumnCount();
		if (cols <= 0)
			return null;
		for (int i = 0; i < cols; i++) {
			Field field = typeFields.get(rsmd.getColumnLabel(i+1).toUpperCase());
			if (field == null)
				throw new InvalidDataAccessApiUsageException("sql column do not match java field,colum:" + rsmd.getColumnLabel(i+1));
			col2FieldMap.put(new Integer(i), field);
		}
		T ret = null;
		try {
			ret = requiredType.newInstance();
		} catch (Exception e) {
			throw new InvalidDataAccessApiUsageException("创建返回对象失败，对象可能没有默认构造方法,requiredType:" + requiredType, e);
		}
		for (int i = 0; i < cols; i++) {
			Field field = col2FieldMap.get(new Integer(i));
			Class type = field.getType();
			Object result = getColumnValue(rs, i+1, type);
			if (result != null && type != null && !type.isInstance(result)) {
				// Extracted value does not match already: try to convert it.
				try {
					result = convertValueToRequiredType(result, type);
				} catch (IllegalArgumentException ex) {
					throw new TypeMismatchDataAccessException("Type mismatch affecting row number " + rowNum
							+ " and column type '" + rsmd.getColumnTypeName(i+1) + "': " + ex.getMessage());
				}
			}
			try {
				ReflectionUtils.makeAccessible(field);
				ReflectionUtils.setField(field, ret, result);
				field.set(ret, result);
			} catch (Exception e) {
				throw new InvalidDataAccessApiUsageException("设置对象field值出错,requiredType:" + requiredType + "\t field:" + field.getName()
						+ "\t value:" + result, e);
			}
		}
		return ret;
	}

	protected Object getColumnValue(ResultSet rs, int index, Class requiredType) throws SQLException {
		if (requiredType != null) {
			return JdbcUtils.getResultSetValue(rs, index, requiredType);
		} else {
			// No required type specified -> perform default extraction.
			return getColumnValue(rs, index);
		}
	}

	protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
		return JdbcUtils.getResultSetValue(rs, index);
	}

	protected Object convertValueToRequiredType(Object value, Class requiredType) {
		if (String.class.equals(requiredType)) {
			return value.toString();
		} else if (Number.class.isAssignableFrom(requiredType)) {
			if (value instanceof Number) {
				// Convert original Number to target Number class.
				return NumberUtils.convertNumberToTargetClass(((Number) value), requiredType);
			} else {
				// Convert stringified value to target Number class.
				return NumberUtils.parseNumber(value.toString(), requiredType);
			}
		} else {
			throw new IllegalArgumentException("Value [" + value + "] is of type [" + value.getClass().getName()
					+ "] and cannot be converted to required type [" + requiredType.getName() + "]");
		}
	}
}
